//
//  HomeViewModel.swift
//  Crypto
//
//  Created by DaleGuo on 2023/9/22.
//

import Combine
import Foundation

class HomeViewModel: ObservableObject {
    @Published var allCoins: [CoinModel] = []
    @Published var portfolioCoins: [CoinModel] = [] // 持有的coins
    @Published var searchText = ""
    @Published var isLoading = false // 是否正在刷新首页数据 联合reloadData
    @Published var stats: [StatisticModel] = []
    @Published var sortOption: SortOption = .holdings

    // 各种services数据源
    private let coinDataService = CoinDataService() // coinDataService.$allCoins 最原始数据
    private let marketDataService = MarketDataService()
    private let portfolioDataService = PortfolioDataService()
    private var cancelables = Set<AnyCancellable>() // 当不需要主动cancel时，就将其存入set中

    /// coin排序
    enum SortOption {
        case rank, rankReversed, holdings, holdingReversed, price, priceReversed
    }

    init() {
        addSubscriber()
    }

    func addSubscriber() {
//        vm的allcoins要去订阅service中的allcoins
        /*
         dataService.$allCoins
             .sink { [weak self] returnedCoins in
                 self?.allCoins = returnedCoins
             }
             .store(in: &cancelables)
         */

        // ！！ 这些是一帘串相互印象的订阅

        // 为输入框过滤，同时订阅输入框和service.coins 以及 排序方式
        $searchText
            .combineLatest(coinDataService.$allCoins, $sortOption)
            .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main) // 去抖
            .map(filterAndSortCoins)
            .sink { [weak self] filteredCoins in
                self?.allCoins = filteredCoins
            }
            .store(in: &cancelables)

        // 订阅用户持有coin
        $allCoins
            .combineLatest(portfolioDataService.$savedEntities)
            .map(
                // 需要将portfolioEntity类型转化为coinModel
                mapAllCoinsToPortfoliosCoins
            )
            .sink { [weak self] coinModels in
                guard let self = self else { return }
                //sink中的数据是不可变的，所以不能用inout
                self.portfolioCoins = self.sortPortfolioInNeeded(portfolioCoins: coinModels)
            }
            .store(in: &cancelables)

        // 订阅marketData 以及 portfolio（用于计算Portfolio value总价值）
        marketDataService.$marketData
            .combineLatest($portfolioCoins)
            .map(mapMarketData)
            .sink { [weak self] mappedStatsValue in
                self?.stats = mappedStatsValue
                self?.isLoading = false // here
            }
            .store(in: &cancelables)
    }

    /// 刷新首页数据（主要是价格）
    func reloadData() {
        isLoading = true // 注意看 是在哪里将其置为false （订阅链中最后一链marketData）
        coinDataService.getCoins()
        marketDataService.getData()
        HapicManager.notification(type: .success) // 震动效果
    }

    func updatePortfolio(coin: CoinModel, amount: Double) {
        portfolioDataService.updatePortfolio(coin: coin, amount: amount)
    }

    private func filterAndSortCoins(text: String, startingCoins: [CoinModel], sort: SortOption) -> [CoinModel] {
        var updatedCoins = filterCoins(text: text, startingCoins: startingCoins)
        // sort
        sortCoins(coins: &updatedCoins, sort: sort)

        return updatedCoins
    }

    private func filterCoins(text: String, startingCoins: [CoinModel]) -> [CoinModel] {
        // 如果searchText，就返回所有
        guard !text.isEmpty else {
            return startingCoins
        }

        let lowerCasedText = text.lowercased()

        let filteredCoins = startingCoins.filter { coin -> Bool in
            coin.name.lowercased().contains(lowerCasedText) || coin.id.contains(lowerCasedText) || coin.symbol.lowercased().contains(lowerCasedText)
        }

        return filteredCoins
    }

    /// 要求传递数组地址， inout在原数组上直接排序，不创建新数组，效率更高，但是线程不安全（读写问题）
    private func sortCoins(coins: inout [CoinModel], sort: SortOption) {
        switch sort {
        case .rank, .holdings:
            // 因为首页不关心hodings，所以～～
            coins.sort { $0.rank < $1.rank }
        case .rankReversed, .holdingReversed:
            coins.sort { $0.rank > $1.rank }
        case .price:
            coins.sort { $0.currentPrice > $1.currentPrice }
        case .priceReversed:
            coins.sort { $0.currentPrice < $1.currentPrice }
        }
    }

    /// coins 已经经过了第一轮订阅者的排序，所以只排序holdings
    private func sortPortfolioInNeeded(portfolioCoins: [CoinModel]) -> [CoinModel] {
        // 这里不用inout是因为sink的数据是不可变的，只能创建新的数据
        switch sortOption {
        case .holdings:
            return portfolioCoins.sorted(by: { $0.currentHoldingsValue > $1.currentHoldingsValue })
        case .holdingReversed:
            return portfolioCoins.sorted(by: { $0.currentHoldingsValue < $1.currentHoldingsValue })
        default:
            return portfolioCoins
        }
    }

    private func mapAllCoinsToPortfoliosCoins(allCoins: [CoinModel], portfoliosEntities: [PortfolioEntity]) -> [CoinModel] {
        allCoins
            .compactMap { coin -> CoinModel? in
                // compactMap返回值不包含nil
                guard let entity = portfoliosEntities.first(where: { portfolioEntity in
                    portfolioEntity.coinID == coin.id
                }) else { return nil }

                return coin.updateHoldings(amount: entity.amount)
            }
    }

    private func mapMarketData(marketDataModel: MarketDataModel?, portfolioCoins: [CoinModel]) -> [StatisticModel] {
        // 要将其转化为StatisticModel
        var stats: [StatisticModel] = []

        guard let data = marketDataModel else {
            return stats
        }
        let marketCap = StatisticModel(title: "Market Cap", value: data.marketCap, percentageChanged: data.marketCapChangePercentage24HUsd)
        let volumn = StatisticModel(title: "24h Volumn", value: data.volume)
        let BTCDominance = StatisticModel(title: "BTC Dominance", value: data.btcDominance)

        let portfolio_value = portfolioCoins.map { $0.currentHoldingsValue }
            .reduce(0, +) // 快速求和

        let previosValue = portfolioCoins.map { coin -> Double in
            let currentValue = coin.currentHoldingsValue
            let percentChange = coin.priceChangePercentage24H ?? 0.0 / 100 // 25 -> 0.25
            let previouseValue = currentValue / (1 + percentChange)
            return previouseValue
        }.reduce(0, +)

        let percentChangeAllPortfolioCoins = (portfolio_value - previosValue) / previosValue

        let portfolioValue = StatisticModel(title: "Portfolio Value", value: portfolio_value.asCurrencyWith2Decimals(), percentageChanged: percentChangeAllPortfolioCoins)
        stats.append(contentsOf: [
            marketCap, volumn, BTCDominance, portfolioValue
        ])

        return stats
    }
}
